﻿using System;
using NHibernate.Mapping.ByCode.Conformist;
using NHibernate.Mapping.ByCode;
using System.Reflection;
using Common.Logging;
using Domain.Model.Commons.Atrributes;
using NHibernate.Type;
using System.Data;
using NHibernate.SqlTypes;

namespace Domain.Model.Commons.Mapping
{
    public abstract class DomainModelMapping<TEntity> : ClassMapping<TEntity> where TEntity : class
    {
        protected ILog logger = LogManager.GetLogger(typeof(TEntity));

        public DomainModelMapping()
        {
            DynamicInsert(true);
            DynamicUpdate(true);
        }

        protected void MappingOthoers(Type entityType)
        {
            MappingClass(entityType, entityType.GetCustomAttributes(false));
            PropertyInfo[] properties = entityType.GetProperties();
            if (null != properties && properties.Length > 0)
            {
                foreach (PropertyInfo entry in properties)
                {
                    MappingProperty(entry, entry.GetCustomAttributes(false));
                }
            }
        }

        private void MappingClass(Type entityType, object[] attributes)
        {
            if (null == attributes || attributes.Length == 0)
            {
                return;
            }
            foreach (object entry in attributes)
            {
                Entity instance = entry as Entity;
                if (null != instance)
                {
                    Table((String.IsNullOrEmpty(instance.TableName) ? entityType.Name : instance.TableName));

                    if (!String.IsNullOrWhiteSpace(instance.Discriminator))
                    {
                        this.Discriminator(map => {
                            map.Column(instance.Discriminator);
                        });
                    }

                    if (instance.IsCache)
                    {
                        Cache(map =>
                        {
                            CacheUsage cacheModel = null;
                                switch (instance.Usage)
                                {
                                    case ECacheUsage.NonstrictReadWrite:
                                        cacheModel = CacheUsage.NonstrictReadWrite;
                                        break;
                                    case ECacheUsage.ReadOnly:
                                        cacheModel = CacheUsage.ReadOnly;
                                        break;
                                    case  ECacheUsage.ReadWrite:
                                        cacheModel = CacheUsage.ReadWrite;
                                        break;
                                    case ECacheUsage.Transactional:
                                        cacheModel = CacheUsage.Transactional;
                                        break;
                                    default :
                                        cacheModel = CacheUsage.ReadWrite;
                                        break;
                                }
                            map.Usage(cacheModel);
                        });
                    }
                    break;
                }
            }
        }

        private void MappingProperty(PropertyInfo propertyInfo, object[] attributes)
        {
            if (null == attributes || attributes.Length == 0)
            {
                return;
            }
            foreach (object entry in attributes)
            {
                MapProperty(propertyInfo, entry as Domain.Model.Commons.Atrributes.Property);
                MapVersion(propertyInfo, entry as Domain.Model.Commons.Atrributes.Version);
                MapManyToOne(propertyInfo, entry as ManyToOne);
                MapOneToMany(propertyInfo, entry as OneToMany);
            }
        }

        private void MapProperty(PropertyInfo info, Domain.Model.Commons.Atrributes.Property attribute)
        {
            if (null == attribute)
            {
                return;
            }
            Property(info.Name, map =>
            {
                map.Column((String.IsNullOrEmpty(attribute.ColumnName) ? info.Name : attribute.ColumnName));
            });
        }

        private void MapManyToOne(PropertyInfo info, ManyToOne attribute)
        {
            if (null == attribute)
            {
                return;
            }
            MethodInfo method = this.GetType().GetMethod("NewManyToOne").MakeGenericMethod(info.PropertyType);
            method.Invoke(this, new object[] { info, attribute });
        }

        public void NewManyToOne<TProperty>(PropertyInfo info, ManyToOne attribute) where TProperty : class
        {
            ManyToOne<TProperty>(info.Name, map =>
            {
                map.Column(attribute.ColumnName);
            });
        }

        private void MapOneToMany(PropertyInfo info, OneToMany attribute)
        {
            if (null == attribute)
            {
                return;
            }

            MethodInfo method = this.GetType().GetMethod("NewSet").MakeGenericMethod(info.PropertyType.DetermineCollectionElementType());
            method.Invoke(this, new object[] { info, attribute });
        }

        public void NewSet<TProperty>(PropertyInfo info, OneToMany attribute) where TProperty : class
        {
            Bag<TProperty>(info.Name, map =>
            {
                map.Key(mapping => { mapping.Column(attribute.MappingColumn); });
                map.Lazy(attribute.Lazy);
                map.Cascade(attribute.CascadeStyle);
            }, action => action.OneToMany());
        }

        private void MapVersion(PropertyInfo info, Domain.Model.Commons.Atrributes.Version attribute)
        {
            if (null == attribute)
            {
                return;
            }
            Version(info.Name, map =>
            {
                map.Column(attribute.ColumnName);
            });
        }
    }

    public class ComponentMapping<TEntity> : DomainModelMapping<TEntity> where TEntity : AbstractComponent
    {
        public ComponentMapping()
        {
            Id(obj => obj.Id, map =>
            {
                map.Column("id");
                map.Generator(Generators.Assigned);
                map.Type(TypeFactory.GetStringType(32) as StringType);
            });

            MappingOthoers(typeof(TEntity));
        }


    }

    public class EntityMapping<TEntity> : DomainModelMapping<TEntity> where TEntity : AbstractEntity
    {
        public EntityMapping()
        {
            Id(obj => obj.Id, map =>
            {
                map.Column("id");
                map.Generator(Generators.Assigned);
            });

            Property(obj => obj.Name, map =>
            {
                map.Column("name");
            });

            Property(obj => obj.CreateTime, map =>
            {
                map.Column("create_time");
            });

            Property(obj => obj.CreateUser, map =>
            {
                map.Column("create_user");
            });

            MappingOthoers(typeof(TEntity));
        }
    }

    public class DictEntityMapping<TEntity> : DomainModelMapping<TEntity> where TEntity : AbstractDictEntity
    {
        public DictEntityMapping()
        {
            Id(obj => obj.Id, map =>
             {
                 map.Column("id");
                 map.Generator(Generators.Assigned);
             });
            Property(obj => obj.Name, map =>
            {
                map.Column("name");
            });

            Property(obj => obj.Priority, map =>
            {
                map.Column("priority");
            });

            Cache(map =>
            {
                map.Usage(CacheUsage.ReadWrite);
            });

            MappingOthoers(typeof(TEntity));
        }
    }

    public class BaseEntityMapping<TEntity> : EntityMapping<TEntity> where TEntity : BaseEntity
    {
        public BaseEntityMapping()
            : base()
        {
            Property(obj => obj.IsDeleted, map => { map.Column("deleted_flag"); });
            Version(obj => obj.Version, map => { map.Column("version"); });
        }
    }
}
